/**
 * 对接4s 网络交易 API的股票买卖自动化
 * 分板4s提供的涨跌趋势以及涨跌幅
 * 统筹管理所有资金,在股票之间调配
 */

var allAssets = 24 * 1000000000000;// ?t的总资产
var stocksKelly = [];// {股票名:kelly投资比}
// 印花税
const stamp_duty = 100000;
// 交易下限使的下限值, 用于调整下限值
const trade_lower_limiting_value2 = stamp_duty*10;
// 放涨跌大倍数, 以放大差值
const rate = 100;
const scalePrice=2;// 赔率为2,对应损失为1.b=b/a
// 赔率参考:https://www.bilibili.com/video/BV1cu411w7Wc
var allStockInfo=[];//是一个对象的列表, 用于调整持仓

// 调式模式
var debugMode = false;

// 全局的ns对象,为了省去函数的参数
var GNS={};
function init(ns){
  GNS = ns;
  allStockInfo=[];
};

/**
 * 每只股票的信息
 */
function getStockInfo(SYM)
{
  var ns = GNS;
  const stockName = SYM;
  var resultInfo={name:SYM};
  resultInfo.forecast = ns.stock.getForecast(SYM);// 预测
  var objPosition = ns.stock.getPosition(SYM);
  resultInfo.takePosition = objPosition[0];// 持仓
  resultInfo.volatility = ns.stock.getVolatility(SYM);// 涨跌幅
  resultInfo.price = ns.stock.getPrice(stockName);// 当前的价格
  resultInfo.maxShares = ns.stock.getMaxShares(SYM);// 总手数, 以免一口气买到上限
  //还有一些api不知道怎么用
  /*
    CACHED_STOCK[6]  = await ns.stock.getSaleGain(SYM, shares, "Long");
    CACHED_STOCK[7]  = await ns.stock.getSaleGain(SYM, shares, "Short");
    CACHED_STOCK[8]  = await ns.stock.getPurchaseCost(SYM, shares, "Long");
    CACHED_STOCK[9]  = await ns.stock.getPurchaseCost(SYM, shares, "Short");
  */
  // ns.tprint("股票:",SYM,"  预测:",resultInfo.forecast,"  持仓:",resultInfo.takePosition
  // ,"  涨跌幅:",resultInfo.volatility,"  当前的价格:",resultInfo.price);
  return resultInfo;
}

/**
 * 算出kelly投资比
 * @param stockInfo
 *                .forecast// 预测
 *                .takePosition// 持仓
 *                .volatility// 涨跌幅
 *                .prict// 当前的价格
 */
function figureOutKelly(stockInfo)
{
  var resultValue = 0.5;
  var b = stockInfo.volatility*rate*2;// 赔率
  var a = stockInfo.volatility*rate;// 损失
  var p = stockInfo.forecast;//赢的概率
  var q = 1-p;//输的概率

  resultValue = (p/a) - (q/b);

  resultValue =(b*p-q)/b;

  //GNS.tprint("股票:",stockInfo.name,"  投资比:",resultValue);
  return resultValue>0?resultValue:0;
}

/**
 * 显示六位数数值,自动算成t/b/m/k这样的单位
 */
export function showNumberIn6(originNumber)
{
  var str1 ;//= ""+originNumber;
  var currentNumber = originNumber;
  var state = 0;// 0:无k 的全数字的情况 1:k 2:m 3:b 4:t
  var postFix = ["","k","m","b","t","q"];
  var str2 =""+originNumber;
  var limit = 100;
  while(str2.length>8 && state<5 && --limit)
  {
    str1 = str2;//作为上一次的参照
    currentNumber.toFixed(3);
    str2 = ""+currentNumber+postFix[state];
    if(str2.length == str1.length)
    {
      currentNumber/=1000;
      ++state;
      var str2=""+currentNumber.toFixed(3)+postFix[state];
    }
  }
  if(0==limit)
  {
    GNS.tprint("有死循环");
  }
  return str2;
}

/**
 * 取股票的中间价的总市值
 * 中间价是历史最高价与历史最低价算出来
 */
function getStockHistoryPosition(stock_name)
{
  let filePath = "/stock/history/history_"+stock_name+"_middle_position.txt";
  let strReadResult = GNS.read(filePath);
  return Number(strReadResult);
}

/**
 * 取股票的历史高位也历史低位
 * @return {minPrice:.. , maxPrice:.. , middlePrice:.. }
 */
function getStockHistoryRecord(stock_name) 
{
  let filePath = "/stock/history/history_"+stock_name+".txt";
  let strReadResult = GNS.read(filePath);
  // 解析文件:
  let historyData={minPrice:0,maxPrice:0,middlePrice:0};//minPrice,maxPrice,middlePrice
  // 字符串分割:
      let arr1 = strReadResult.split("\n");
      for(var index in arr1)
      {
        if(0==arr1[index].length)continue;
        // ns.tprint("\t",arr1[index]);
        // 进一步分割字符串:
        let arr2 = arr1[index].split(":");
        if("minPrice"== arr2[0])
        {
          // ns.tprint("字符串0:",arr2[0],"正在分割的字串:",arr1[index]);
          historyData.minPrice = Number(arr2[1]);
        }
        else if("maxPrice"== arr2[0])
        {
          historyData.maxPrice = Number(arr2[1]);
        }
        else if("middlePrice"== arr2[0])
        {
          historyData.middlePrice = Number(arr2[1]);
        }
      }
  // GNS.tprint("读文件的结果:",historyData);
  return historyData;
}

/**
 * 第一步, 先算出所有股票的kelly
 */
function figure_out_all_stocks_Kelly_value()
{
  let symbols = GNS.stock.getSymbols();
  for(let i=0;i<symbols.length;++i)
  {
    let SYM = symbols[i];
    // 1.取单只股票的数据
    var objStockInfo = getStockInfo(SYM);
    // 2.算出kelly值 (kellyValue)
    objStockInfo.kellyValue = figureOutKelly(objStockInfo);
    // 算出排序值,为了应对总市值极小的股票
    objStockInfo.sortVaule = objStockInfo.kellyValue
      *(getStockHistoryPosition(SYM)/(allAssets/33));
    // 3.根据kelly值插入列表
    if(0==allStockInfo.length)
    {
      allStockInfo.push(objStockInfo);
    }
    else
    {
      // 1. 找插入点
      var insertPos=0;
      var small = false;
      // GNS.tprint("####数组大小",allStackInfo.length);
      for(let i=0;i<allStockInfo.length;++i)
      {
        // GNS.tprint("####",objStockInfo.kellyValue ,"<=", allStackInfo[i].kellyValue);
        if(objStockInfo.sortVaule <= allStockInfo[i].sortVaule)
        {
          insertPos = i;
          small = true;// 有比它大的
          break;
        }
        else
        {
          //GNS.tprint("####break");
          //break;
        }
      }// END for
      if(small)
      {
        // GNS.tprint("####往后调一步");
        // 往后调一步
        for(let i=allStockInfo.length;i>insertPos;--i)
        {
          allStockInfo[i]=allStockInfo[i-1];
        }// END for 往后调一步
        allStockInfo[insertPos]=objStockInfo;
      }
      else
      {
        // GNS.tprint("####push");
        allStockInfo.push(objStockInfo);
      }

      // GNS.tprint("----insertPos:",insertPos);
      // for(let i=0;i<allStackInfo.length;++i)
      // {
      //   GNS.tprint(allStackInfo[i].kellyValue,"\t:\t",allStackInfo[i].name);
      // }// END for
      // GNS.tprint("####");
    }
  }// END for symbols

  // 输出最终的排序结果
  if(debugMode)
  {
    GNS.tprint("列表中有",allStockInfo.length,"条数据");
    for(let i=0;i<allStockInfo.length;++i)
    {
      GNS.tprint(allStockInfo[i].sortVaule,"\t",allStockInfo[i].kellyValue,"\t:\t",allStockInfo[i].name);
    }// END for
  }

}

/**
 * 所有股票资产
 */
function totalStockValue()
{
  var resultSum=0;
  let symbols = GNS.stock.getSymbols();
  for(let i=0;i<symbols.length;++i)
  {
    let SYM = symbols[i];
    // 1.取单只股票的数据
    var objStockInfo = getStockInfo(SYM);
    /*
    var objPosition = ns.stock.getPosition(SYM);
    resultInfo.takePosition = objPosition[0];// 持仓
    resultInfo.volatility = ns.stock.getVolatility(SYM);// 涨跌幅
    resultInfo.price = ns.stock.getPrice(stockName);// 当前的价格
    */
    resultSum+=objStockInfo.takePosition*objStockInfo.price;
  }
  return resultSum;
}

/**
 * 调仓位
 */
function adjustPos()
{
  var momeyRemain = allAssets;
  let countTotalAsset = 0;// 统计所有的资产, 没法统计, 因为每一轮的累加是不公平的
  for(let i=0;i<allStockInfo.length;++i)
  {
    // 当前的剩余资金由剩下的股票平分
    var remainStocksNumber = allStockInfo.length-i;
    var objStockInfo = allStockInfo[i];
    //GNS.tprint("----",objStockInfo.name,"----");
    // 交易下限值:
    var trade_lower_limiting_value=stamp_duty*100;

    // 多少钱分配给这一只股票
    var momeyOfThisStock = momeyRemain/remainStocksNumber;
    // 这只股票的总市值:
    var totalMoneyOfThisStock = objStockInfo.maxShares*objStockInfo.price;
    const middle_position_of_this_stock = getStockHistoryPosition(objStockInfo.name);
      
    if(momeyOfThisStock>middle_position_of_this_stock)
    {
      momeyOfThisStock = middle_position_of_this_stock;      
      // 进一步防顶天:
      // 算出这只股票的总值:totalMoneyOfThisStock, 然后如果还是过大则调整
      if(totalMoneyOfThisStock<momeyOfThisStock)
      {
        let middle_position_of_this_stock_2 = middle_position_of_this_stock;
        //GNS.tprint(objStockInfo.name," 配额从",showNumberIn6(momeyOfThisStock),"调整到:",showNumberIn6(totalMoneyOfThisStock));
        //momeyOfThisStock = totalMoneyOfThisStock*.5;// 这样的话, 跟着涨跌, 失去了做T的机会, 涨的话, 股标中资金占总资金的比例也跟着涨.
        // 给每只股票的配额一直在变, 这是个问题. 分配后就不能定下来吗?
        let debug_value_old_momeyOfThisStock = momeyOfThisStock;
        while(totalMoneyOfThisStock<momeyOfThisStock)
        {
          momeyOfThisStock*=.9;// 之前*.5, 成绩是15b/s 但这样的话, 还是趋近于以totalMoneyOfThisStock为配额
          // 改成.9后, 曾到34b/s 两小时, 现在到了28b/s 估计稳定后还是那样. 不久又涨到30b/s // 有40b/s的情况.
        }
        //GNS.tprint(objStockInfo.name," 配额从",showNumberIn6(debug_value_old_momeyOfThisStock),"调整到:",showNumberIn6(momeyOfThisStock));
      }

      // if("SGC" == objStockInfo.name)
      // {
      //   GNS.tprint(objStockInfo.name,"受限于历史中位:",showNumberIn6(middle_position_of_this_stock)," 不然准备:",showNumberIn6(momeyRemain/remainStocksNumber));
      // }
      // 交易下限调整:
      var target_lower_limit = middle_position_of_this_stock*.1;
      if(target_lower_limit<trade_lower_limiting_value2)target_lower_limit=trade_lower_limiting_value2;
      if(trade_lower_limiting_value>target_lower_limit)trade_lower_limiting_value=target_lower_limit;
      
      // if("SGC" == objStockInfo.name)
      // {
      //   GNS.tprint(objStockInfo.name,"交易下限:"
      //   ,showNumberIn6( trade_lower_limiting_value)
      //   ,"  target_lower_limit:",target_lower_limit
      //   );
      // }
    }


    // 用股票在历史中的位置来影响股票的购买量, 结果是, 赚钱的速度没涨. 那有没有降? 也没看出有降, 1小时的采样了不多.
    // let objHistoryData = getStockHistoryRecord(objStockInfo.name); 
    // let minPrice = objHistoryData.minPrice;
    // let maxPrice = objHistoryData.maxPrice;
    // let deltaPrice = maxPrice-minPrice;
    // let delta2 = objHistoryData.middlePrice - objStockInfo.price;
    // let inffectRate = 2;//delta2 /deltaPrice;// 2024-05-30 试试单单翻两倍
    // let delta3 = 0;
    // if(0!=deltaPrice)
    // {
    //   // delta3 = Math.pow(delta2/deltaPrice,2);
    //   inffectRate = 1 + delta2/deltaPrice;
    //   inffectRate = Math.pow(inffectRate,2);
    //   if(inffectRate<1)inffectRate=1;// 因为高位的股票也会涨, 而且高位的股票如果波动大, 就赚得多. 目前赚钱的速度低到9b/s,如果没这段有20b/s
    //   //把整段去掉的另一个因素: 低位的股票, 也会跌, 资金放多了就亏得多.
    // }


    // GNS.tprint(objStockInfo.name,"当前价:",showNumberIn6(objStockInfo.price),"\t中间价:"
    //   ,showNumberIn6(objHistoryData.middlePrice)
    //   ,"\t比例:",inffectRate
    //   );


    // countTotalAsset +=momeyOfThisStock;
    // 当前这只股票的持仓总额
    var momeyInStock = objStockInfo.takePosition*objStockInfo.price;
    // if("SGC" == objStockInfo.name)
    // {
    //   GNS.tprint(objStockInfo.name,"股市里的资产:",showNumberIn6( momeyInStock));
    // }
    // 这只股票应该的持仓总额:
    var momeyAdjustTarget = momeyOfThisStock*objStockInfo.kellyValue ;// * inffectRate;
    // if("SGC" == objStockInfo.name)
    // {
    //   GNS.tprint(objStockInfo.name,"目标的持仓:"
    //   ,showNumberIn6(momeyAdjustTarget)
    //   ,"  kellyValue:",objStockInfo.kellyValue
    //   ,"  momeyOfThisStock:",showNumberIn6(momeyOfThisStock)
    //   );
    // }
    // momeyOfThisStock是目前这只股票的总额, 分现金与股票两部分
    // 如果不用调整的话, 现金部分应该是:
    //var cashShouldRemain = momeyOfThisStock - momeyAdjustTarget;//毕竟momeyAdjustTarget是个逻辑值,会有误差
    //var cashShouldRemain = momeyOfThisStock - momeyInStock;
    // // 若之后有调整,该剩下多少钱:
    // var cashInFactRemain = cashShouldRemain;

    // 两者差额
    var delta = momeyAdjustTarget-momeyInStock;
    // // 这只股票,已经在股票里的配额
    // GNS.tprint("momeyRemain若-=momeyAdjustTarget:", showNumberIn6( momeyRemain-momeyAdjustTarget));
    // GNS.tprint("momeyRemain若-=momeyInStock:", showNumberIn6(momeyRemain-momeyInStock));
    // momeyRemain-=momeyAdjustTarget;

    // 差值有印花税的100倍则调整    *2 是为了让两边最终能持平
    if(Math.abs(delta)> trade_lower_limiting_value*2)
    {
      //if(debugMode)GNS.tprint(objStockInfo.name,"该调 kelly:",objStockInfo.kellyValue,"  现有:",momeyInStock,"  目标:",momeyAdjustTarget );
      if(delta>0)
      {
        // 买几只:
        var numberShares = Math.floor( (Math.abs(delta)/2)/objStockInfo.price)  ;
        if(numberShares> ( objStockInfo.maxShares-objStockInfo.takePosition) )
        {
          numberShares= Math.floor(( objStockInfo.maxShares-objStockInfo.takePosition)/2);
        }
        if(debugMode)GNS.tprint(objStockInfo.name,"买<<<<<<<",numberShares);
        var originShares = objStockInfo.takePosition;
        GNS.stock.buyStock(objStockInfo.name, numberShares);
        var afterShares = GNS.stock.getPosition(objStockInfo.name)[0];
        if(afterShares-originShares !=numberShares)
        {
          GNS.print("买入失败,买入:",numberShares,"\t实际买入:",afterShares-originShares
            ,"\t当前持有:",objStockInfo.takePosition);
          // momeyRemain -=(afterShares-originShares)*objStockInfo.price
          // momeyRemain -=stamp_duty;
          // cashInFactRemain-=(afterShares-originShares)*objStockInfo.price;
          // cashInFactRemain-=stamp_duty;
          if(afterShares-originShares>0)
          {
            momeyRemain -=(afterShares-originShares)*objStockInfo.price;
            momeyRemain -=stamp_duty;
            // if("SGC" == objStockInfo.name)
            // {
            //   GNS.tprint("SGC","买入<<<",afterShares-originShares,"手");
            // }
          }
          else
          {
            GNS.print("再次买入:");
            var limit_times = 100;
            while(0==afterShares-originShares && --limit_times)
            {
              numberShares = Math.floor(numberShares/2);
              GNS.stock.buyStock(objStockInfo.name, numberShares);
              afterShares = GNS.stock.getPosition(objStockInfo.name)[0];
            }
            GNS.print("再次买入完成");
          }
        }
        else
        {
          momeyRemain -= numberShares*objStockInfo.price;// Math.abs(delta)/2;
          momeyRemain -= stamp_duty;
          // if("SGC" == objStockInfo.name)
          // {
          //   GNS.tprint("SGC","买入<<<",numberShares,"手");
          // }
          // cashInFactRemain -= Math.abs(delta)/2;
          // cashInFactRemain -= stamp_duty;
        }
        // allAssets-=numberShares*objStockInfo.price;
        // allAssets-=stamp_duty;
        // countTotalAsset-=numberShares*objStockInfo.price;
        // countTotalAsset-=stamp_duty;
      }
      else
      {
        var numberShares = Math.floor( (Math.abs(delta)/2)/objStockInfo.price);
        if(debugMode)GNS.tprint(objStockInfo.name,"卖>>>>>>",numberShares);
        var originShares = objStockInfo.takePosition;
        GNS.stock.sellStock(objStockInfo.name, numberShares);
        var afterShares = GNS.stock.getPosition(objStockInfo.name)[0];
        if(originShares-afterShares != numberShares)
        {
          //GNS.tprint("卖出失败,卖出:",numberShares,"\t实际买入:",originShares-afterShares);
          momeyRemain+=(originShares-afterShares)*objStockInfo.price;
          //cashInFactRemain+=(originShares-afterShares)*objStockInfo.price;;
        }
        else
        {
          // allAssets+=numberShares*objStockInfo.price;
          // allAssets-=stamp_duty;
          // countTotalAsset+=numberShares*objStockInfo.price;
          // countTotalAsset-=stamp_duty;
          momeyRemain += numberShares*objStockInfo.price;// Math.abs(delta)/2;
          // if("SGC" == objStockInfo.name)
          // {
          //   GNS.tprint("SGC","卖出",numberShares,"手");
          // }
          //cashInFactRemain+=numberShares*objStockInfo.price;
          // var delta1 = (Math.abs(delta)/2 - stamp_duty);
          // var delta2 = numberShares*objStockInfo.price - stamp_duty;
          // if(delta1<delta2)
          // {
          //   GNS.tprint("有少算",delta2-delta1);
          // }

        }
      }
      // 调整后的股票资产
      countTotalAsset+=GNS.stock.getPosition(objStockInfo.name)[0]*objStockInfo.price;
    }
    else
    {
      // if("SGC" == objStockInfo.name)GNS.tprint(objStockInfo.name,"差额不够大");
      countTotalAsset+=momeyInStock;
    }
    momeyRemain -= (GNS.stock.getPosition(objStockInfo.name)[0]*objStockInfo.price);
    // GNS.tprint("在这只股票上的花费:", showNumberIn6( momeyOfThisStock-cashInFactRemain)
    //   ,"\t目前股票总价值:",showNumberIn6(GNS.stock.getPosition(objStockInfo.name)[0]*objStockInfo.price)
    //   ,"\tmomeyRemain:",showNumberIn6(momeyRemain)
    //   );
  }// END 
  // GNS.tprint("预备金还剩下:",momeyRemain);
  // GNS.tprint("预备金还剩下:",  showNumberIn6(momeyRemain));
  // GNS.tprint("总资产还剩下:",  allAssets);
  // GNS.tprint("总资产还剩下:",  showNumberIn6(allAssets));
  var allStockValue = totalStockValue();
  // GNS.tprint("所有股票资产:",showNumberIn6(allStockValue),"\t花剩下的钱:",showNumberIn6(momeyRemain));
  var value_of_assessment = countTotalAsset+momeyRemain;//allStockValue*1.4;
  //if(value_of_assessment > allAssets)
  // if(value_of_assessment*.999 > allAssets )
  // {
  //   allAssets*=1.001;
  // }
  // else
  // {
  //   allAssets*=0.999;
  // }
  allAssets = value_of_assessment;
  //GNS.tprint((countTotalAsset+momeyRemain),"\t\t",allAssets,"\t花剩的钱:",momeyRemain);
  //GNS.tprint("总资产预估:",showNumberIn6(value_of_assessment)
  //   ,"\t调整到:",showNumberIn6(value_of_assessment));
  GNS.print(" 股票资产:",showNumberIn6(countTotalAsset)
    ," 现金资产:",showNumberIn6(momeyRemain)
    ," 总资产:",showNumberIn6(value_of_assessment));
  
}// END adjustPos

// function GetTotalAsset()
// {
//   var resultSum=0;
//   for(let i=0;i<allStockInfo.length;++i)
//   {
//     // 当前的剩余资金由剩下的股票平分
//     var remainStocksNumber = allStockInfo.length-i;
//     var objStockInfo = allStockInfo[i];
//     // countTotalAsset +=momeyOfThisStock;
//     // 当前这只股票的持仓总额
//     var momeyInStock = objStockInfo.takePosition*objStockInfo.price;
//     // 这只股票应该的持仓总额:
//     var momeyAdjustTarget = momeyOfThisStock*objStockInfo.kellyValue;
//     if(objStockInfo.kellyValue)
//     {
//       resultSum+=(momeyInStock)/objStockInfo.kellyValue; 
//     }
//   }
// }



/** 主函数 */
export async function main(ns) {
  while(true)
  {
    init(ns);  
    // 第一步, 先算出所有股票的kelly
    figure_out_all_stocks_Kelly_value();
    // 4. 开始调仓位
    adjustPos();
    // GNS.tprint("-------------------------------------------");
    // break;
    // 记录每只股票的历史最高点最低点, 算出中间价对应的总市值, 用作每只股票的配额上限
    await ns.run("/stock/stockHistoryPrice.js");
    await ns.sleep(4000);
  }
}